<?php

/**
 * @copyright Michiel Hakvoort 2010
 * @license http://www.opensource.org/licenses/bsd-license.php New BSD
 * @package mangrove
 * @subpackage groove
 * @filesource
 */

/*
 * Copyright (c) 2010 Michiel Hakvoort
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

namespace mg;

interface InputStream {

    public function close();

    public function mark();

    public function reset();

    public function read();

    public function seek($position);

    public function advance($amount);

    public function regress($amount);

    public function offset();
}

class StringInputStream implements InputStream {

    private $value;

    private $mark = 0;
    private $pos = 0;

    public function __construct($value) {
        $this->value = $value;
    }

    public function close() {
    }

    public function mark() {
        $this->mark = $this->pos;
    }

    public function reset() {
        $this->pos = $this->mark;
    }

    public function seek($offset) {
        $this->pos = $offset;
    }

    public function read() {
        if($this->pos === mb_strlen($this->value)) {
            return -1;
        }

        $return = mb_substr($this->value, $this->pos, 1); //$this->value{$this->pos};

        $this->pos++;

        return $return;
    }

    public function advance($amount) {
        $this->pos = min(mb_strlen($this->value), $this->pos + $amount);
    }

    public function regress($amount) {
        $this->pos = max(0, $this->pos - $amount);
    }

    public function offset() {
        return $this->pos;
    }
}

class LineCountInputStream implements InputStream {

    private $inputStream;

    private $newLines = array(0);
    private $maxOffset = -1;

    public function __construct(InputStream $inputStream) {
        $this->inputStream = $inputStream;
    }

    public function close() {
        $this->inputStream->close();
    }

    public function mark() {
        $this->inputStream->mark();
    }

    public function reset() {
        $this->inputStream->reset();
    }

    public function read() {
        $char = $this->inputStream->read();
        if($this->inputStream->offset() > $this->maxOffset) {
            $this->maxOffset = $this->inputStream->offset();
            if($char == "\n") {
                $this->newLines[] = $this->maxOffset;
            }
        }

        return $char;
    }

    public function getPosition() {
        $lastLine = end($this->newLines);

        $offset = $this->offset();

        $line = count($this->newLines);
        while($lastLine >= $offset && $line > 1) {
            $line--;
            $lastLine = prev($this->newLines);
        }

        $line--;

        $lineOffset = $offset - $this->newLines[$line];
        $lineOffset--;

        return new Point($line, $lineOffset);
    }

    public function getLine() {
        $lastLine = end($this->newLines);

        $offset = $this->offset();

        $line = count($this->newLines);
        while($lastLine >= $offset && $line > 1) {
            $line--;
            $lastLine = prev($this->newLines);
        }

        return $line - 1;
    }

    public function getOffset($offset = -1) {
        $offset = $this->offset();
        $line = $this->getLine($offset);
        return $offset - $this->newLines[$line] - 1;
    }

    public function advance($amount) {
        $this->inputStream->advance($amount);
    }

    public function seek($offset) {
        $this->inputStream->seek($offset);
    }

    public function regress($amount) {
        $this->inputStream->regress($amount);
    }

    public function offset() {
        return $this->inputStream->offset();
    }
}
